let Vue

const forEach = (obj, callback) => {
  Object.keys(obj).forEach((key) => {
    callback(key, obj[key])
  })
}

class ModuleCollection {
  constructor(options) {
    // 深度遍历将所有的子模块都遍历一遍
    this.register([], options)
  }
  register(path, rootModule) {
    let rawModule = {
      _raw: rootModule,
      _children: {},
      state: rootModule.state
    }
    // 双向记录
    rootModule.rawModule = rawModule

    if (!this.root) {
      this.root = rawModule
    } else {
      // 递归寻找父模块
      const parentModule = path.slice(0, -1).reduce((root, current) => {
        return root._children[current]
      }, this.root)
      parentModule._children[path[path.length - 1]] = rawModule
    }

    if (rootModule.modules) {
      forEach(rootModule.modules, (moduleName, module) => {
        // 将 a 模块进行注册 [a], a 模块的定义

        // 将 b 模块进行注册 [b], b 模块的定义
        // 将 c 模块进行注册 [b, c], c 模块的定义
        this.register(path.concat(moduleName), module)
      })
    }
  }
}

function getState(store, path) {
  const local = path.reduce((newState, current) => {
    return newState[current]
  }, store.state)
  return local
}

function installModule(store, rootState, path, rawModule) {
  let getters = rawModule._raw.getters
  // 获取到了 最终整个的格式化的结果
  let root = store.modules.root
  let namespaced = path.reduce((str, current) => {
    root = root._children[current]
    str = str + (root._raw.namespaced ? current + '/' : '')
    return str
  }, '')
  // 子模块
  if (path.length > 0) {
    let parentState = path.slice(0, -1).reduce((root, current) => {
      return root[current]
    }, rootState)
    Vue.set(parentState, path[path.length - 1], rawModule.state)
  }

  if (getters) {
    // 定义 getters
    forEach(getters, (getterName, value) => {
      Object.defineProperty(store.getters, namespaced + getterName, {
        get: () => {
          // 模块中的状态
          return value(getState(store, path))
        }
      })
    })
  }
  let mutations = rawModule._raw.mutations
  if (mutations) {
    forEach(mutations, (mutationName, value) => {
      let arr =
        store.mutations[namespaced + mutationName] ||
        (store.mutations[namespaced + mutationName] = [])
      arr.push((payload) => {
        value(getState(store, path), payload)

        store.subs.forEach((fn) =>
          fn({ type: namespaced + mutationName, payload: payload }, store.state)
        )
      })
    })
  }

  let actions = rawModule._raw.actions
  if (actions) {
    forEach(actions, (actionName, value) => {
      let arr =
        store.actions[namespaced + actionName] ||
        (store.actions[namespaced + actionName] = [])
      arr.push((payload) => {
        value(store, payload)
      })
    })
  }

  forEach(rawModule._children, (moduleName, rawModule) => {
    installModule(store, rootState, path.concat(moduleName), rawModule)
  })
}
class Store {
  constructor(options) {
    this.strict = options.strict || false
    this._committing = false // 默认是没有提交

    this.vm = new Vue({
      data() {
        return {
          state: options.state
        }
      }
    })

    this.subs = []
    this.getters = {}
    this.mutations = {}
    this.actions = {}

    // 需要将用户传入的数据进行格式化操作
    this.modules = new ModuleCollection(options)
    // console.log(this.modules)

    const plugins = options.plugins
    plugins.forEach((p) => p(this))
    //  let root = {
    //    _raw: rootModule,
    //    state: rootModule.state,
    //    _children: {
    //      a: {
    //        _raw: aModule,
    //        _children: {},
    //        state: aModule.state
    //      },
    //      b: {
    //       _raw: bModule,
    //       _children: {},
    //       state: bModule.state
    //     }
    //    }
    //  }
    // 递归安装模块
    installModule(this, this.state, [], this.modules.root)

    if (this.strict) {
      this.vm.$watch(
        () => {
          return this.vm.state
        },
        () => {
          console.assert(this._committing, '不能异步调用')
        },
        {
          deep: true,
          sync: true // 同步执行
        }
      )
    }
  }
  _withCommit(fn) {
    const committing = this._committing
    this._committing = true // 默认调用mutation之前 先更改值是 true
    fn()
    this._committing = committing
  }
  subscribe(fn) {
    this.subs.push(fn)
  }
  replaceState(newState) {
    this._withCommit(() => {
      this.vm.state = newState
    })
  }
  get state() {
    return this.vm.state
  }
  // 箭头函数的 this,永远指向当前 store 实例
  commit = (mutationName, payload) => {
    this._withCommit(() => {
      // 装饰模式
      // 发布
      this.mutations[mutationName].forEach((fn) => fn(payload))
    })
  }
  dispatch = (actionName, payload) => {
    this.actions[actionName].forEach((fn) => fn(payload))
  }
  registerModule(moduleName, module) {
    if (!Array.isArray(moduleName)) {
      moduleName = [moduleName]
    }
    this._withCommit(() => {
      this.modules.register(moduleName, module)
      // 只安装当前的模块
      installModule(this, this.state, moduleName, module.rawModule)
    })
  }
}

const install = (_Vue) => {
  Vue = _Vue
  // 组件创建的顺序是先父后子 挂载的顺序是先子后父
  Vue.mixin({
    beforeCreate() {
      if (this.$options.store) {
        this.$store = this.$options.store
      } else {
        this.$store = this.$parent && this.$parent.$store
      }
    }
  })
}

export const mapState = (stateArr) => {
  const obj = {}
  stateArr.forEach((stateName) => {
    obj[stateName] = function () {
      return this.$store.state[stateName]
    }
  })
  return obj
}

export const mapGetters = (gettersArr) => {
  const obj = {}
  gettersArr.forEach((getterName) => {
    obj[getterName] = function () {
      return this.$store.getters[getterName]
    }
  })
  return obj
}

export const mapMutations = (obj) => {
  const result = {}
  Object.entries(obj).forEach(([key, value]) => {
    result[key] = function (...args) {
      this.$store.commit(value, ...args)
    }
  })
  return result
}

export const mapActions = (obj) => {
  const result = {}
  Object.entries(obj).forEach(([key, value]) => {
    result[key] = function (...args) {
      this.$store.dispatch(value, ...args)
    }
  })
  return result
}

export default {
  Store,
  install
}
